//C- -------------------------------------------------------------------
//C- Java DjVu (r) (v. 0.8)
//C- Copyright (c) 2004-2005 LizardTech, Inc.  All Rights Reserved.
//C- Java DjVu is protected by U.S. Pat. No.C- 6,058,214 and patents
//C- pending.
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- The computer code originally released by LizardTech under this
//C- license and unmodified by other parties is deemed "the LIZARDTECH
//C- ORIGINAL CODE."  Subject to any third party intellectual property
//C- claims, LizardTech grants recipient a worldwide, royalty-free,
//C- non-exclusive license to make, use, sell, or otherwise dispose of
//C- the LIZARDTECH ORIGINAL CODE or of programs derived from the
//C- LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
//C- General Public License.   This grant only confers the right to
//C- infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
//C- the extent such infringement is reasonably necessary to enable
//C- recipient to make, have made, practice, sell, or otherwise dispose
//C- of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
//C- any greater extent that may be necessary to utilize further
//C- modifications or combinations.
//C-
//C- The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C-
//C- In addition, as a special exception, LizardTech Inc. gives permission
//C- to link the code of this program with the proprietary Java
//C- implementation provided by Sun (or other vendors as well), and
//C- distribute linked combinations including the two. You must obey the
//C- GNU General Public License in all respects for all of the code used
//C- other than the proprietary Java implementation. If you modify this
//C- file, you may extend this exception to your version of the file, but
//C- you are not obligated to do so. If you do not wish to do so, delete
//C- this exception statement from your version.
//C- -------------------------------------------------------------------
//C- Developed by Bill C. Riemers, Foxtrot Technologies Inc. as work for
//C- hire under US copyright laws.
//C- -------------------------------------------------------------------
//
package com.lizardtech.djvu{
import flash.utils.ByteArray;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.Integer;

[RemoteClass(alias="com.lizardtech.djvu.CachedInputStream")]

  /**
 * This class is an InputStream which spans some of the accessable data in a
 * DataPool object.
 * 
 * @author Bill C. Riemers
 * @version $Revision: 1.5 $
 */
public class CachedInputStream
  extends InputStream
  implements DjVuInterface
{
    //~ Instance fields ------------------------------------------------------

    // Buffer used to access data
    public var buffer:DataPool = new DataPool();
    
    // Used for the mark and reset features.
    private var markOffset:int=0;

    // The current position of this stream in the data pool.
    private var offset:int=0;

    // The end position of this stream in the data pool.
    private var endOffset:int=Integer.MAX_VALUE;

    // Index of the current data block
    private var blockIndex:int=-1;
    
    // The data block currently being read
    private var block:ByteArray = new ByteArray();
    
    // Object for holding the DjVuOptions
    private var djvuObject:DjVuObject= new DjVuObject();

    // We can assign a name to this Stream
    private var name:String = "";

    //~ Constructors ---------------------------------------------------------

    /**
     * Creates a new CachedInputStream object.
     */
    public function CachedInputStream()
    {
    	trace("CIS: here");
    }

    //~ Methods --------------------------------------------------------------

  /**
   * Creates an instance of CachedInputStream with the options interherited from the
   * specified reference.
   *
   * @param ref Object to interherit DjVuOptions from.
   *
   * @return a new instance of CachedInputStream.
   */
  public static function createCachedInputStream(ref:DjVuInterface):CachedInputStream{
    var options:DjVuOptions= ref.getDjVuOptions();

    return DjVuObject.create(
      options,
      options.getCachedInputStreamClass(),
      "com.lizardtech.djvu.CachedInputStream") as CachedInputStream; 
  }

  /**
   * Set the DjVuOptions used by this object.
   *
   * @param options The DjVuOptions used by this object.
   */
  public function setDjVuOptions(options:DjVuOptions):void{
    djvuObject.setDjVuOptions(options);
  }

  /**
   * Query the DjVuOptions used by this object.
   *
   * @return the DjVuOptions used by this object.
   */
  public function getDjVuOptions():DjVuOptions{
    return djvuObject.getDjVuOptions();
  }


    /**
     * Create an a new CachedInputStream.
     *
     * @param size the maximum amount of data to read from this stream
     *
     * @return the newly created CachedInputStream
     */
    public final function createCachedInputStream(size:int):CachedInputStream{
      var retval:CachedInputStream = this; 
      
      /*
      var retval:CachedInputStream = new CachedInputStream();
      var buffer:ByteArray = new ByteArray();
      this.writeBytes(buffer, 0, size);
      retval.init(buffer, 0, size);
      retval.setSize(size);
      */
      
      return retval;
    }

    /**
     * Initialize the stream with a data source, startOffset, and endOffset.
     * 
     * @param buffer DataPool containing data.
     * @param startOffset Starting position of this stream.
     * @param endOffset Ending position of this stream.
     * 
     * @return the initialized stream
     */
    public final function init(buffer:*,startOffset:int,endOffset:int):CachedInputStream{
      if(buffer is DataPool) {
	      this.buffer=buffer;
	      markOffset=offset=startOffset;
	      this.endOffset=endOffset;
	      return this;
      } else if (buffer is InputStream) {
	      if(buffer is CachedInputStream) {
	        var i:CachedInputStream = buffer as CachedInputStream;
	        return init(i.buffer,i.offset,i.getEndOffset());         
	      } else {
	        return init(DataPool.createDataPool(this).init(buffer as InputStream), 0, buffer.bytesAvailable);
	      }
      }
      return null;
    }
   
    
    /**
     * Create a copy of this stream which will referes to the same DataPool
     * 
     * @return the newly created copy
     */
    public override function clone():*{
      var clone:CachedInputStream = new CachedInputStream();
      clone.cloneInit(buffer.clone() as DataPool, markOffset, offset, endOffset, blockIndex, java.util.cloner.clone(block) as ByteArray, djvuObject.clone() as DjVuObject, name);
      return clone;
    }
    
    public function cloneInit(dp:DataPool, mo:int, off:int, eo:int, bi:int, bl:ByteArray, dj:DjVuObject, n:String):void {
    	this.buffer = dp;
    	this.markOffset = mo;
    	this.offset = off;
    	this.blockIndex = bi;
    	this.block = bl;
    	this.djvuObject = dj;
    	this.name = n;
    }

    /**
     * Query the end position.  This value may only be reduced, never increased.
     *
     * @return an offset past the last byte of this stream
     */
    public final function getEndOffset():int{
       var endOffset:int = buffer.getEndOffset();
      return (endOffset<this.endOffset)?endOffset:this.endOffset;
    }

    
    /**
     * Set the maximum number of bytes left in this stream.  
     * Size may only be reduced, never increased.
     *
     * @param size the new size
     */
    public final function setSize(size:int):void{
      var endOffset:int=offset+size;
      if(endOffset < this.endOffset)
      {
        this.endOffset=int(endOffset);
      }
    }
    
    /**
     * Query how much data is available.
     *
     * @return number of buffered bytes
     */
    public final function available():int{
      var position:int= buffer.getCurrentSize();
      var retval:int=(position < endOffset)?position:endOffset;
      return (retval > 0)?retval:0;
    }

    /**
     * Marks the current location for a reset() later.
     *
     * @param readLimit ignored
     */
    public function mark(readLimit:int):void{
      markOffset = offset;
    }

    /**
     * Query if mark is supported.
     *
     * @return true
     */
    public function markSuppoted():Boolean{
      return true;
    }


    /**
     * Read data 
     *
     * @param b byte array to copy data to
     * @param off byte array offset to start copying to
     * @param len maximum number of bytes to copy
     *
     * @return number of bytes read
     */
    public override function read(
       b:Array=null, //array
       off:int=0, //int
       len:int=0):int{ //int
      var retval:int = -1;
      var index:int=offset/DataPool.BLOCKSIZE;
      //trace("read: [" + offset +","+ index + "] "  + off + "," + len);
	  

	  if(b == null) {   
	      if(index != blockIndex) {
	        block=buffer.getBlock(index, true);
	        blockIndex=index;
	        if(block == null)
	        {
	          offset = getEndOffset();
	          blockIndex = -1;
	        }
	      }
	      if(offset < getEndOffset())
	      {
	        retval=0xff & block[offset++%DataPool.BLOCKSIZE];
	      }
      	  return retval;
      } else if (len == 0) {
	      for(var remaining:int = b.length; remaining > 0;)
	      {
	        var retval:int = b.length - remaining;
	        var len:int = read(b, retval, remaining);
	        if(len < 0) {
	          return (retval > 0) ? retval:(-1);              
	        }
	        remaining -= len;
	      }
	      return b.length;	
	  } else {
	      if(index != blockIndex) {
	        block = buffer.getBlock(index, true);
	        blockIndex = index;
	        if(block == null) {
	          offset = getEndOffset();
	          blockIndex = -1;
	        }
	      }
	      if(offset < getEndOffset())
	      {
	        var offset:int=this.offset % DataPool.BLOCKSIZE;
	        retval = DataPool.BLOCKSIZE - offset;
	        if(retval > len) {
	          retval = len;
	        }
	        //System.arraycopy(block, offset,b,off,retval);
	        for(var sc:int=0; sc<retval; sc++) {
	        	b[off+sc] = block[offset+sc];
	        }
	        this.offset += retval;
	      }
	      //trace("returned " + retval + " bytes");
	      return retval;
	  }        
    }

    /**
     * Read the next two bytes as a posative integer.  If EOF is reached,
     * then return -1.
     *
     * @return the value read
     */
    public function read16():int{
      var msb:int= read();

      if(msb < 0)
      {
        return msb;
      }

      var lsb:int= read();

      return (lsb >= 0)
      ? ((msb << 8) | lsb)
      : (-1);
    }

    /**
     * Read the next three bytes as a posative integer.  If EOF is reached,
     * then return -1.
     *
     * @return the value read
     */
    public function read24():int{
      var msb:int = read16();

      if(msb < 0)
      {
        return msb;
      }

      var lsb:int= read();

      return (lsb >= 0)
      ? ((msb << 8) | lsb)
      : (-1);
    }

    /**
     * Restore the marked position
     *
     * @throws IOException if an error occurs
     */
    public function reset():void 
    {
      offset = markOffset;
    }

    /**
     * Skip bytes without reading.
     *
     * @param n number of bytes to skip
     *
     * @return number of bytes actually skipped
     */
    public final function skip(n:Number):Number{
      var endOffset:int=getEndOffset();
      var retval:int=((endOffset )<= n+(offset))?(endOffset-offset):int(n);
      if(retval > 0)
      {
        offset+=retval;
      }
      else
      {
        retval=0;
      }
      return retval;
    }
    
  /**
   * Prefetch data in the same thread.
   */
  public function prefetchWait():void{
    try
    {
      for(var i:int=offset/DataPool.BLOCKSIZE;i<getEndOffset()/DataPool.BLOCKSIZE;i++)
      {
        buffer.getBlock(i,true);
      }
      buffer.getBlock((getEndOffset()-1)/DataPool.BLOCKSIZE, true);
    }
    catch(ignored:*) {}
  }
  
  /**
   * Convert the accessable data into a string.  First a java modified UTF-8
   * translation will be tried.  If that fails, the data will be converted
   * in the current locale encoding.  Unlike ImageInputStream.readUTF
   * the first two bytes are not a length.
   *
   * @param textsize maximum amount of data to read
   *
   * @return the newly created string
   *
   * @throws IOException if an error occurs
   */
  public function readSizedUTF(textsize:int):String 
  {
    var retval:String = createCachedInputStream(textsize).readFullyUTF();
    skip(textsize);
    return retval;
  }
  
  /**
   * Convert the accessable data into a string.  First a java modified UTF-8
   * translation will be tried.  If that fails, the data will be converted
   * in the current locale encoding.  Unlike an ImageInputStream.readUTY
   * the first two bytes are not a length.
   *
   * @return the newly created string
   *
   * @throws IOException if an error occurs
   */
  public function readFullyUTF():String
  {
    var output:ByteArray = new ByteArray();
    output.writeByte(0);
    output.writeByte(0);
    for(var i:int=read();i>=0;i=read())
    {
      output.writeByte(i);
    }
    var array:ByteArray = output;
    try
    {
    	/*
      // The string is too long for readUTF.  Try using standard UTF-8.  This
      // will fail with the Microsoft JVM.
      if(array.length > 65537)
      {
        return new String(array, 2, array.length - 2, "UTF-8");
      }

      // Try reading modified UTF-8.  This should be supported with all
      // versions of Java.
      array[0]=byte(((array.length-2)>>8));
      array[1]=byte(((array.length-2)&0xff));
      final var input:DataInputStream=
        new DataInputStream(new ByteArrayInputStream(array));

      return input.readUTF();
      */
      return array.readUTF();
    }
    catch(exp:*) {
      exp.printStackTrace();//(DjVuOptions.err);
      //System.gc();
    }

	var oString:String;
	oString = array.readUTFBytes(array.length - 2);
	
    return oString.substr(2);
  }
  
  /**
   * Query the name of this stream.
   *
   * @return the stream name
   */
  public function getName():String{
    return name;
  }
  
  /**
   * Set the name of this stream.
   *
   * @param name the stream name
   */
  public final function setName(name:String):void{
    this.name=name;
  }
  
  /**
   * Test if the underlying file has a DjVu octet signature.
   *
   * @return true if this is a valid DjVu file.
   */
  public function isDjVuFile():Boolean{
    var ba:ByteArray=buffer.getBlock(0,true);
    
    var b:Array = new Array(b.bytesAvailable);
    for(var cc:uint=0;cc<b.length;cc++) {
    	b[cc] = ba.readByte();
    }
    
    return (b != null)&&(b[0] == 65)&&(b[1] == 84)&&(b[2] == 38)&&(b[3] == 84);
  }

  /**
   * Query the enumeration of IFF chunks.  Returns null, if the name is 
   * four characters long.
   *
   * @return an Enumeration of CachedInputStream or null.
   */
  public function getIFFChunks():Enumeration{
    var retval:IFFEnumeration=null;
    if ((this.name == null) || (this.name.length != 4))
    {
      retval=IFFEnumeration.createIFFEnumeration(this).init(this);
    }
    return retval;
  }
 
  public function setPositionByOffset(index:int):void {
  	this.position = index;
  	offset = this.position;
  }
  
  public function setPositionByIndex(index:int):void {
  	this.position = index * DataPool.BLOCKSIZE;
  	offset = this.position;
  }
    
  public function resetPosition():void {
  	this.position = 0;
  	offset = 0;
  }
}}